import { EventEmitter } from 'events';
import * as SocketIO from 'socket.io';

import { getLogger } from 'log4js';
const logger = getLogger('Peer');

import { ROLE } from './defines';
import { Room } from './Room';

interface AppData {
    intervalHandler: number;
}
export interface Producer {
    id: string;
    kind: string;
    type: string;
    closed: boolean;
    close();
    clear();
}
export interface Transport {
    id: string;
    closed: boolean;
    close();
    clear();
}
export interface Consumer {
    id: string;
    kind: string;
    type: string;
    producerId: string;
    closed: boolean;
    close();
    clear();
    appData: AppData
}

export class Peer extends EventEmitter {
    roler: ROLE;
    producers = new Map<string, Producer>();
    transports = new Map<string, Transport>();
    consumers = new Map<string, Consumer>();
    closed = false;
    joined = false;
    displayName: string;
    picture: string;
    platform: string;
    address: string;
    enterTime: number = Date.now();

    disconnectCheck = 0;
    intervalHandler;

    rtpCapabilities: any;

    constructor(public id: string, public socket: SocketIO.Socket, public room: Room) {
        super();

        logger.info('constructor() [id: "%s", socket: "%s"', id, socket.id);

        this.address = socket.handshake.address;
        this.setMaxListeners(Infinity);
        this.handlePeer();
    }

    close() {
        logger.info('Peer %s call close()', this.id);

        this.closed = true;
        this.closeResource();

        if (this.socket) {
            this.socket.disconnect(true);
        }

        if (this.intervalHandler) {
            clearInterval(this.intervalHandler);
        }

        this.emit('close');
    }

    public handlePeerReconnect(socket: SocketIO.Socket) {
        this.socket.leave(this.room.id);
        this.socket.disconnect(true);
        logger.info('Peer %s reconnected! Disconnect previous connection now.', this.id);

        this.socket = socket;
        this.socket.join(this.room.id);
        this.room.setupSocketHandler(this);
        this.handlePeer();
    }

    private closeResource() {
        this.producers.forEach(producer => producer.close());

        this.consumers.forEach(consumer => { clearInterval(consumer.appData.intervalHandler); consumer.close(); });

        this.transports.forEach(transport => { transport.close(); });

        this.transports.clear();
        this.producers.clear();
        this.consumers.clear();
    }

    private handlePeer() {
        this.socket.on('disconnect', (reasons) => {
            if (this.closed) {
                return;
            }
            logger.debug('"Socket disconnect" event [id: %s], reason: %s', this.id, reasons);

            this.disconnectCheck = 0;
            if (this.intervalHandler) {
                clearInterval(this.intervalHandler);
            }

            this.intervalHandler = setInterval(() => {
                this.checkClose();
            }, 20000);
        });

        this.socket.on('error', (error) => {
            logger.info('Socket error, peer: %s, error: %s', this.id, error);
        });
    }

    public checkClose(): void {
        if (!this.socket.connected) {
            this.disconnectCheck++;
        } else {
            clearInterval(this.intervalHandler);
            this.intervalHandler = null;
        }

        if (this.disconnectCheck > 6) {
            this.close();
        }
    }

    addTransport(id: string, transport: Transport) {
        this.transports.set(id, transport);
    }

    getTransport(id: string) {
        return this.transports.get(id);
    }

    getConsumerTransport() {
        return Array.from(this.transports.values()).find((t: any) => t.appData.consuming);
    }

    removeTransport(id: string) {
        this.transports.delete(id);
    }

    addProducer(id: string, producer: Producer) {
        this.producers.set(id, producer);
    }

    getProducer(id: string) {
        this.producers.get(id);
    }

    removeProducer(id: string) {
        this.producers.delete(id);
    }

    addConsumer(id: string, consumer: Consumer) {
        this.consumers.set(id, consumer);
    }

    getConsumers(id: string) {
        return this.consumers.get(id);
    }

    removeConsumer(id: string) {
        const consumer = this.consumers.get(id);
        if (consumer) {
            consumer.close();
            clearInterval(consumer.appData.intervalHandler);
        }
        this.consumers.delete(id);
    }

    statusReport() {
        let transportReport = new Array<any>();
        this.transports.forEach(value => {
            transportReport.push({
                transportId: value.id,
                closed: value.closed,
            });
        });

        let producerReport = new Array<any>();
        this.producers.forEach(value => {
            producerReport.push({
                producerId: value.id,
                closed: value.closed,
                kind: value.kind,
                type: value.type,
            });
        });

        let consumerReport = new Array<any>();
        this.consumers.forEach(value=>{
            consumerReport.push({
                consumerId: value.id,
                closed: value.closed,
                kind: value.kind,
                producerId: value.producerId,
                type: value.type,
            });
        });

        return {
            ...this.peerInfo(),
            joined: this.joined,
            closed:this.closed,
            transports: transportReport,
            producers: producerReport,
            consumers: consumerReport,
        };
    }

    peerInfo(){
        const peerInfo = {
            id: this.id,
            roler: this.roler,
            displayName: this.displayName,
            picture: this.picture,
            platform: this.platform,
            address: this.address,
            durationTime: (Date.now() - this.enterTime) / 1000,
        };

        return peerInfo;
    }
}
